//Event Based Two Way Communication
//http://rxtx.qbang.org/wiki/index.php/Event_based_two_way_Communication
//But with my (Richard Nguyen) annotation/comments added to explain the code
//make sure you copied over the jar and dll files to this project from the RXTX library
//make sure you set up the Native library location to this project folder as well as import the library
//always update the Native library location if you rename the project folder
//make sure you have something plugged into COM3 per the code

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * This version of the TwoWayEventSerialComm example makes use of the 
 * SerialPortEventListener to avoid polling.
 *
 */
public class TwoWayEventSerialComm
{
    public TwoWayEventSerialComm()
    {
        super();
    }
    
    void connect ( String portName ) throws Exception//takes in the COM port
    {
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);//get COM port information
		//i.e. Get COM port object which contains information (see CommPortIdentifier constructor) such as name given to this port, Instantiation of CommPort implementation, serial or parallel port type, and the driver) 
        if ( portIdentifier.isCurrentlyOwned() )//Checks whether the port is owned
        {
            System.out.println("Error: Port is currently in use");
        }
        else
        {
            CommPort commPort = portIdentifier.open(this.getClass().getName(),2000);//opens the COM port
			//getClass comes from the class java.lang.Object which is inherited. It returns the runtime class (not the class name in a string but the actual class) hence the return type is Class<?> which is a Class object of wildcard type
			//Refer to: http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
			//Also refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html#open(java.lang.String, int)
			
			//Opens the communications port. Passes the name of the COM port as well as set the timeout to 2000
			//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html
			//when the comm port is open, it returns a CommPort object. "There are no public constructors for CommPort. Instead an application should use the static method CommPortIdentifier.getPortIdentifiers to generate a list of available ports."
			//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPort.html
            
            
            if ( commPort instanceof SerialPort )//checks to see if the commPort is a SerialPort
			//since commPort is an abstract class which SerialPort and ParallelPort extends from
            {
                SerialPort serialPort = (SerialPort) commPort;//type cast the commPort reference to a serialPort reference
                serialPort.setSerialPortParams(57600,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);//Sets serial port parameters
				//setSerialPortParams(int baudrate, int dataBits, int stopBits, int parity) 
                
                InputStream in = serialPort.getInputStream();//gets the Input Stream object of the serial port and takes the "in" reference and points it to that object
				//getInputStream() is inherited from CommPort according to the API "Methods inherited from class javax.comm.CommPort"
				//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPort.html#getInputStream()
				
                OutputStream out = serialPort.getOutputStream();//gets the Output Stream object of the serial port and takes the "out" reference and points it to that object
				//getOutputStream() is inherited from CommPort according to the API "Methods inherited from class javax.comm.CommPort"
				//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPort.html#getOutputStream()
                               
                (new Thread(new SerialWriter(out))).start();//create a new Thread for writing to the serial port. Passes the "out" Output Stream object reference that is linked to the serialPort object from above
				//Note: SerialWriter class is defined below in this code
                
                serialPort.addEventListener(new SerialReader(in));//Registers a SerialPortEventListener object to listen for SerialEvents.
				//Note: SerialReader class is defined below in this code
				//creates an event listener to trigger (versus having to constantly poll) to the SerialReader
				//passes the "in" Input Stream object reference  (that came from the serialPort object reference) to the constructor of a SerialReader object (which implements the interface SerialPortEventListener) which is passed to addEventListener
				//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/SerialPort.html
	
                serialPort.notifyOnDataAvailable(true);//Expresses interest in receiving notification when input data is available.
				
				//Note: Make sure that you call the notifyOnDataAvailable() method of the SerialPort with a boolean true parameter. Based on my experience with RXTX, just registering to the SerialPort as a SerialPortEventListener is not enough -- the event will not be propagated unless you do this.
				//Refer to: http://rxtx.qbang.org/wiki/index.php/Event_based_two_way_Communication


            }
            else//if the commport wasn't a serialport type, print the following message
            {
                System.out.println("Error: Only serial ports are handled by this example.");
            }
        }     
    }
    
    /**
     * Handles the input coming from the serial port. A new line character
     * is treated as the end of a block in this example. 
     */
    public static class SerialReader implements SerialPortEventListener//reads the serial message from the serial comm port
	//SerialReader class has to implement the interface SerialPortEventListener (which itself implements the interface java.util.EventListener) in order to have SerialReader be able to be passed to addEventListener() and have it listen for Serial Port events
    {
        private InputStream in;//instance variable
        private byte[] buffer = new byte[1024];//serial message data buffer, byte array
        
        public SerialReader ( InputStream in )
        {
            this.in = in;//set the InputSteam passed to the constructor to the instance's instance variable in
        }
        
        public void serialEvent(SerialPortEvent arg0) {//overrides the SerialPortEventListener's interface
		//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/SerialPortEventListener.html
            int data;
          
            try
            {
                int len = 0;//used to hold the index of the buffer as well as keep track of the length of the buffer
                while ( ( data = in.read()) > -1 )//Calls the overloaded public abstract int read()
				//Reads the next byte of data from the input stream's reference "in"
				//refer to http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#read()
				//Note: the next byte of data, or -1 if the end of the stream is reached.
                {
                    if ( data == '\n' ) {//when detect a newline character from the serial port message, break out of the while loop
                        break;
                    }
                    buffer[len++] = (byte) data;//increment the index/and length tracker, "len" variable, of the buffer
                }
                System.out.print(new String(buffer,0,len));//creates a new string with 0 offset and with length of the buffer and pring it out to the console
				//Using this overloaded constructor for string: String(byte[] bytes, int offset, int length)
				//http://docs.oracle.com/javase/6/docs/api/java/lang/String.html
            }
            catch ( IOException e )
            {
                e.printStackTrace();
                System.exit(-1);
            }             
        }

    }

    /** */
    public static class SerialWriter implements Runnable//reads the serial message from the serial comm port
	//SerialWriter class has to implement runnable in order to be used in a thread
    {
        OutputStream out;
        
        public SerialWriter ( OutputStream out )
        {
            this.out = out;//take the OutputStream passed in as a parameter and set it to the instance's instance variable "out"
        }
        
        public void run ()//needs the run() method as part of the implementation of Runnable to have it work with threads
        {
            try
            {                
                int c = 0;
                while ( ( c = System.in.read()) > -1 )//Reads the next byte of data from the input stream coming from the System console
				//refer: http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#read()
				//Note: the next byte of data, or -1 if the end of the stream is reached.
                {
                    this.out.write(c);//Writes the specified byte to the output stream called "out" which belongs to "this" SerialWriter object
					//refer: http://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html
                }                
            }
            catch ( IOException e )
            {
                e.printStackTrace();
                System.exit(-1);
            }            
        }
    }
    

    
    public static void main ( String[] args )
    {
        try
        {
            (new TwoWayEventSerialComm()).connect("COM3");//create a TwoWayEventSerialComm object and call the connect method, and use COMM3
			//the connect method creates a new thread for serial write and an event listener for serial read
        }
        catch ( Exception e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


}